/* eeprom in flash
 * Copyright (c) 2021-2022 epoko
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "eeprom_in_flash.h"

static idata uint8_t EEPROM_data[EEPROM_NUM_MAX];
static idata int8_t EEPROM_PART_USE = -1;

static uint8_t EE_Init(void);
static uint8_t EE_ReadVariable(uint8_t VirtAddress, uint8_t* Data);
static uint8_t EE_WriteVariable(uint8_t VirtAddress, uint8_t Data);
static uint8_t EE_Format(uint8_t part);

static uint8_t EE_ErasePart(uint8_t part);
static uint8_t EE_ProgramByte(uint16_t Address, uint8_t Data);
static uint8_t EE_ReadByte(uint16_t Address);
#define EE_ReadHalfWord(a) ((((uint16_t)EE_ReadByte(a))<<8)|EE_ReadByte(a+1))

uint8_t EEPROM_Init(void)
{
	uint8_t i;
	
	for(i=0; i<EEPROM_NUM_MAX; i++)
		EEPROM_data[i]=0;
	
	if(EE_Init() != HAL_OK)
	{
		log_printf("EEPROM Init err\n");
		return HAL_ERROR;
	}
	
	for(i=0; i<EEPROM_NUM_MAX; i++)
		EE_ReadVariable(i, &EEPROM_data[i]);
	log_printf("EEPROM Init OK\n");
	return HAL_OK;
}


uint8_t EEPROM_Read(uint8_t Address)
{
	if(Address>=EEPROM_NUM_MAX)
		return 0;
	
	return EEPROM_data[Address];
}

uint8_t EEPROM_Write(uint8_t Address, uint8_t Data)
{
	if(EEPROM_PART_USE == -1 || Address>=EEPROM_NUM_MAX)
		return HAL_ERROR;
	
	if(EEPROM_data[Address] == Data)
		return HAL_OK;
	
	if(EE_WriteVariable(Address, Data) != HAL_OK)
	{
//		EEPROM_data[Address] = Data;
		log_printf("write err\n");
		return HAL_ERROR;
	}
	
	return HAL_OK;
}

uint16_t EEPROM_ReadWORD(uint8_t Address)
{
	return (EEPROM_Read(Address)<<8)|EEPROM_Read(Address+1);
}

uint8_t EEPROM_WriteWORD(uint8_t Address, uint16_t Data)
{
	if(EEPROM_Write(Address, Data>>8) != HAL_OK)
		return HAL_ERROR;
	if(EEPROM_Write(Address+1, Data) != HAL_OK)
		return HAL_ERROR;
	return HAL_OK;
}

/* -------------- EE Private function -------------- */
static uint8_t EE_Init(void)
{
	if(EE_ReadHalfWord(PART0_BASE_ADDRESS) == PART_USED_MARK)
	{
		EEPROM_PART_USE = 0;
	}
	else if(EE_ReadHalfWord(PART1_BASE_ADDRESS) == PART_USED_MARK)
	{
		EEPROM_PART_USE = 1;
	}
	else
	{
		if(EE_Format(0) != HAL_OK)
			return HAL_ERROR;
		EEPROM_PART_USE = 0;
	}

	return HAL_OK;
}

static uint8_t EE_ReadVariable(uint8_t VirtAddress, uint8_t* Data)
{
	uint16_t PartStartAddress;
	uint16_t Address;
	PartStartAddress = (EEPROM_PART_USE==0?PART0_BASE_ADDRESS:PART1_BASE_ADDRESS);
	Address = (EEPROM_PART_USE==0?PART0_END_ADDRESS:PART1_END_ADDRESS) - 2;
	
	for(; Address>PartStartAddress; Address-=2)
	{
		if(EE_ReadByte(Address) == VirtAddress)
		{
			*Data = EE_ReadByte(Address + 1);
			log_printf("Read %#bx in %#bx at %#x\n", *Data, VirtAddress, Address);
			return HAL_OK;
		}
	}
	log_printf("Read failed\n");
	return HAL_ERROR;
}

static uint8_t EE_WriteVariable(uint8_t VirtAddress, uint8_t Data)
{
	uint16_t PartStartAddress = (EEPROM_PART_USE==0?PART0_BASE_ADDRESS:PART1_BASE_ADDRESS);
	uint16_t Address = (EEPROM_PART_USE==0?PART0_END_ADDRESS:PART1_END_ADDRESS) - 2;
	
	if(EE_ReadByte(Address) != 0xff || EE_ReadByte(Address+1) != 0xff)
	{
		EEPROM_data[VirtAddress] = Data;
		if(EE_Format(EEPROM_PART_USE==0?1:0) != HAL_OK)
			return HAL_ERROR;
		
		EEPROM_PART_USE = EEPROM_PART_USE==0?1:0;

	#if PART0_BASE_ADDRESS!=PART1_BASE_ADDRESS
		if(EE_ErasePart(EEPROM_PART_USE==0?1:0) != HAL_OK)
			return HAL_ERROR;
	#endif
	
		return HAL_OK;
	}
	else
	{
		for(Address-=2; Address>PartStartAddress; Address-=2)
		{
			if(EE_ReadByte(Address) != 0xff || EE_ReadByte(Address+1) != 0xff)
				break;
		}
		Address+=2;
		log_printf("Prog %#bx in %#bx at %#x\n", Data, VirtAddress, Address);
		if(EE_ProgramByte(Address+1, Data) != HAL_OK)
			return HAL_ERROR;
		if(EE_ProgramByte(Address, VirtAddress) != HAL_OK)
			return HAL_ERROR;
		
		EEPROM_data[VirtAddress] = Data;
		return HAL_OK;
	}
}

static uint8_t EE_Format(uint8_t part)
{
	uint8_t i;
	uint16_t Address = (part==0?PART0_BASE_ADDRESS:PART1_BASE_ADDRESS) + 2;
	
	if(EE_ErasePart(part) != HAL_OK)
		return HAL_ERROR;
	
	for(i=0; i<EEPROM_NUM_MAX; i++)
	{
		log_printf("Prog %#bx in %#bx at %#x\n", EEPROM_data[i], i, Address);
		if(EE_ProgramByte(Address+1, EEPROM_data[i]) != HAL_OK)
			return HAL_ERROR;
		if(EE_ProgramByte(Address, i) != HAL_OK)
			return HAL_ERROR;
		
		Address+=2;
	}
	
	Address = (part==0?PART0_BASE_ADDRESS:PART1_BASE_ADDRESS);
	log_printf("Prog %#x at %#x\n", PART_USED_MARK, Address);
	if(EE_ProgramByte(Address, PART_USED_MARK>>8) != HAL_OK)
		return HAL_ERROR;
	if(EE_ProgramByte(Address+1, PART_USED_MARK) != HAL_OK)
		return HAL_ERROR;
	
	return HAL_OK;
}

/* ------------------ flash port  ------------------ */
uint8_t IAP_Erase(uint16_t addr)
{
	uint8_t ret;
	log_printf("Erase at %#x\n", addr);
	IAP_CONTR = 0x80;
	IAP_TPS = 12;	//12MHz
	IAP_CMD = 3;	//erase
	IAP_ADDRL = addr;
	IAP_ADDRH = addr>>8;
	IAP_TRIG = 0x5a;
	IAP_TRIG = 0xa5;
	_nop_();
	ret = IAP_CONTR&0x10;
	IAP_CONTR = 0x00;
	IAP_TRIG = 0x00;
	return ret;
}

uint8_t EE_ErasePart(uint8_t part)
{
	//EEPROM_PART0 0x000 - 0x800 size: 2048 page: 512
	//EEPROM_PART1 0x800 - 0x1000 size: 2048 page: 512
	if(part==0)
	{
		if(IAP_Erase(PART0_BASE_ADDRESS)) goto error;
		if(IAP_Erase(PART0_BASE_ADDRESS+0x200)) goto error;
		if(IAP_Erase(PART0_BASE_ADDRESS+0x400)) goto error;
		if(IAP_Erase(PART0_BASE_ADDRESS+0x600)) goto error;
	}
	else
	{
		if(IAP_Erase(PART1_BASE_ADDRESS)) goto error;
		if(IAP_Erase(PART1_BASE_ADDRESS+0x200)) goto error;
		if(IAP_Erase(PART1_BASE_ADDRESS+0x400)) goto error;
		if(IAP_Erase(PART1_BASE_ADDRESS+0x600)) goto error;
	}
	return HAL_OK;
error:
	log_printf("Erase err\n");
	return HAL_ERROR;
}

uint8_t EE_ProgramByte(uint16_t Address, uint8_t Data)
{
	if(Address<PART0_BASE_ADDRESS || Address>=PART1_END_ADDRESS)
		return HAL_ERROR;
	
	IAP_CONTR = 0x80;
	IAP_TPS = 12;	//12MHz
	IAP_CMD = 2;	//program
	IAP_ADDRL = Address;
	IAP_ADDRH = Address>>8;
	IAP_DATA = Data;
	
	IAP_TRIG = 0x5a;
	IAP_TRIG = 0xa5;
	_nop_();
	
	if(IAP_CONTR&0x10)
	{
		IAP_CONTR = 0x00;
		IAP_TRIG = 0x00;
		log_printf("Prog err\n");
		return HAL_ERROR;
	}
	
	IAP_CONTR = 0x00;
	IAP_TRIG = 0x00;
	return HAL_OK;
}

uint8_t EE_ReadByte(uint16_t Address)
{
	Address += IAP_OFFSET;
//	log_printf("FLASHE Read %x at %#x\n", Data, Address);
	
	return *(char code*)(Address);
}
	